<template>
	<Teleport to="body">
		<div
			class="ContextMenu ContextMenuLevel1 noSelect"
			:data-theme="isDarkTheme ? 'dark' : 'light'"
			:id="id.replace('#', '')"
			:style="{
				display: visibleComp(1),
				width: Number.parseInt(width + '') + 'px',
				top: topComp(1),
				left: leftComp(1),
			}"
		>
			<div
				v-for="item in menuDataComp(1)"
				class="menu"
				:class="{
					activeMenu: activeMenuList.find((subItem) => subItem.value === item.value),
				}"
				:id="'level1-' + (item.value || item.label)"
				:key="item.value || item.label"
				:data-disabled="item.disabled"
			>
				<div class="box" v-if="!item.slot" @click="click(item, 1)" @mouseenter="hover(item, 1)">
					<div
						class="content"
						:style="{
							color: item.color || '',
							backgroundColor: item.bgColor || '',
						}"
					>
						<div class="left">
							<div class="iconBox" v-if="hasIcon(1)">
								<YhIcon v-if="item.icon" :name="item.icon"></YhIcon>
							</div>
							<span>{{ item.label }}</span>
						</div>
						<div
							class="right"
							:style="{
								color: item.descColor || '',
							}"
						>
							<YhIcon
								v-if="item?.children?.length"
								name="ArrowRightBold"
								style="font-size: 11px; transform: translateX(2px); margin-left: 15px"
							></YhIcon>
							<span v-if="!item?.children?.length && item.desc" style="margin-left: 15px">{{ item.desc }}</span>
						</div>
					</div>
				</div>

				<div class="box boxSlot" v-else @click="click(item, 1)" @mouseenter="hover(item, 1)">
					<slot :name="item.value"></slot>
				</div>

				<div class="divider" v-if="item.divider"></div>
			</div>
		</div>

		<div
			class="ContextMenu ContextMenuChildren ContextMenuLevel2 noSelect"
			v-if="hasChildren(2)"
			:data-theme="isDarkTheme ? 'dark' : 'light'"
			:id="'level2ContextMenu-' + id.replace('#', '')"
			:style="{
				display: visibleComp(2),
				top: topComp(2),
				left: leftComp(2),
			}"
		>
			<div
				v-for="item in menuDataComp(2)"
				class="menu"
				:class="{
					activeMenu: activeMenuList.find((subItem) => subItem.value === item.value),
				}"
				:id="'level2-' + (item.value || item.label)"
				:key="item.value || item.label"
				:data-disabled="item.disabled"
			>
				<div class="box" v-if="!item.slot" @click="click(item, 2)" @mouseenter="hover(item, 2)">
					<div
						class="content"
						:style="{
							color: item.color || '',
							backgroundColor: item.bgColor || '',
						}"
					>
						<div class="left">
							<div class="iconBox" v-if="hasIcon(2)">
								<YhIcon v-if="item.icon" :name="item.icon"></YhIcon>
							</div>
							<span>{{ item.label }}</span>
						</div>
						<div
							class="right"
							:style="{
								color: item.descColor || '',
							}"
						>
							<YhIcon
								v-if="item?.children?.length"
								name="ArrowRightBold"
								style="font-size: 11px; transform: translateX(2px); margin-left: 15px"
							></YhIcon>
							<span v-if="!item?.children?.length && item.desc" style="margin-left: 15px">{{ item.desc }}</span>
						</div>
					</div>
				</div>

				<div class="box boxSlot" v-else @click="click(item, 2)" @mouseenter="hover(item, 2)">
					<slot :name="item.value"></slot>
				</div>

				<div class="divider" v-if="item.divider"></div>
			</div>
		</div>

		<div
			class="ContextMenu ContextMenuChildren ContextMenuLevel3 noSelect"
			v-if="hasChildren(3)"
			:data-theme="isDarkTheme ? 'dark' : 'light'"
			:id="'level3ContextMenu-' + id.replace('#', '')"
			:style="{
				display: visibleComp(3),
				top: topComp(3),
				left: leftComp(3),
			}"
		>
			<div
				v-for="item in menuDataComp(3)"
				class="menu"
				:class="{
					activeMenu: activeMenuList.find((subItem) => subItem.value === item.value),
				}"
				:id="'level3-' + (item.value || item.label)"
				:key="item.value || item.label"
				:data-disabled="item.disabled"
			>
				<div class="box" v-if="!item.slot" @click="click(item, 3)" @mouseenter="hover(item, 3)">
					<div
						class="content"
						:style="{
							color: item.color || '',
							backgroundColor: item.bgColor || '',
						}"
					>
						<div class="left">
							<div class="iconBox" v-if="hasIcon(3)">
								<YhIcon v-if="item.icon" :name="item.icon"></YhIcon>
							</div>
							<span>{{ item.label }}</span>
						</div>
						<div
							class="right"
							:style="{
								color: item.descColor || '',
							}"
						>
							<span v-if="item.desc" style="margin-left: 15px">{{ item.desc }}</span>
						</div>
					</div>
				</div>

				<div class="box boxSlot" v-else @click="click(item, 3)" @mouseenter="hover(item, 3)">
					<slot :name="item.value"></slot>
				</div>

				<div class="divider" v-if="item.divider"></div>
			</div>
		</div>
	</Teleport>
</template>

<script setup lang="ts">
import { getParentNodeList } from '/@/components/ContextMenu/index';
import type { PropType } from 'vue';
import { YhIcon } from 'yh-ui';

type MenuDataType = {
	// 名称
	label?: string;
	// 值
	value?: string | number;
	// 左侧图标
	icon?: string;
	// 右侧描述
	desc?: string;
	// 禁用
	disabled?: boolean;
	// 字体颜色
	color?: string;
	// 右侧描述字体颜色
	descColor?: string;
	// 背景颜色
	bgColor?: string;
	// hover时字体颜色
	hoverColor?: string;
	// hover时右侧描述字体颜色
	hoverDescColor?: string;
	// hover时背景颜色
	hoverBColor?: string;
	// 此菜单下方显示分割线
	divider?: boolean;
	// 自定义菜单内容（插槽名为value值）
	slot?: boolean;
	// 子菜单（仅支持三层级菜单）(如有子菜单，则每个菜单项必须写value，且务必保证所有value不重复)
	children?: MenuDataType[];
};

/*
 * 当在同一个vue页面中使用多个“触发范围有包含关系”的菜单组件时（无包含关系就不存在问题）
 * 如果菜单触发的生效顺序不符合预期
 * 请按照范围由小到大，依次书写（被包含的写在上面）
 * */
const props = defineProps({
	// 菜单的全局唯一id，确保不会重复
	id: {
		type: String,
		required: true,
		default: '',
	},
	// 宽度
	width: {
		type: [String, Number],
		default: 200,
	},
	// 在哪个/哪些元素中可以使用右键菜单功能（传入css选择器）
	enableMenuDom: {
		type: String,
		default: 'body',
	},
	// 菜单数据
	menu: {
		type: Array as PropType<MenuDataType[]>,
		default: () => [],
	},
});

// 检测系统是否为暗色主题
const colorSchemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
const isDarkTheme = ref(colorSchemeQuery.matches);
colorSchemeQuery.addEventListener('change', (e) => {
	isDarkTheme.value = e.matches;
});
const emits = defineEmits(['update:showMenu', 'click', 'hover']);
// 各级菜单组件Dom
let ContextMenuDomList = ref<HTMLElement[]>([]);
// 所有合法范围区域的Dom列表
let RootDomList = ref<NodeListOf<HTMLElement>>();
// 各级菜单的宽度
const menuWidthList = ref<number[]>([]);
// 各级菜单的高度
const menuHeightList = ref<number[]>([]);
// 各级别菜单的top坐标
const menuTopList = ref<number[]>([]);
// 各级别菜单的left坐标
const menuLeftList = ref<number[]>([]);

// 选中的所有菜单项
const activeMenuList = ref<MenuDataType[]>([]);

// 一级菜单显示隐藏
const level1ContextMenuVisible = ref<boolean>(false);
// 二级菜单显示隐藏
const level2ContextMenuVisible = ref<boolean>(false);
// 三级菜单显示隐藏
const level3ContextMenuVisible = ref<boolean>(false);

// 菜单数据中是否有带图标的菜单项
const hasIcon = computed(() => {
	return (level: number) => {
		let flag = false;
		if (level === 1) {
			flag = props.menu.some((menu) => menu.icon);
		}
		if (level === 2) {
			props.menu.forEach((item) => {
				if (item?.children?.length) {
					flag = item.children.some((menu) => menu.icon);
				}
			});
		}
		if (level === 3) {
			props.menu.forEach((item) => {
				if (item?.children?.length) {
					item.children.forEach((subItem) => {
						if (subItem.children?.length) {
							flag = subItem.children.some((menu) => menu.icon);
						}
					});
				}
			});
		}
		return flag;
	};
});
// 菜单数据中是否有带子级的菜单项
const hasChildren = computed(() => {
	return (level: number) => {
		let flag = false;
		if (level === 2) {
			flag = props.menu.some((menu) => menu?.children?.length);
		}
		if (level === 3) {
			props.menu.forEach((item) => {
				if (item?.children?.length) {
					if (!flag) flag = item.children.some((menu) => menu?.children?.length);
				}
			});
		}
		return flag;
	};
});

// 计算菜单数据
const menuDataComp = computed(() => {
	return (level: number) => {
		if (level === 1) return props.menu;
		if (level === 2) return activeMenuList.value[0]?.children || [];
		if (level === 3) return activeMenuList.value[1]?.children || [];
		return [];
	};
});
// 计算菜单显示隐藏
const visibleComp = computed(() => {
	return (level: number) => {
		let flag;
		if (level === 1) flag = level1ContextMenuVisible.value;
		if (level === 2) flag = level2ContextMenuVisible.value;
		if (level === 3) flag = level3ContextMenuVisible.value;
		return flag ? 'flex' : 'none';
	};
});
// 计算top坐标
const topComp = computed(() => {
	return (level: number) => {
		if (+menuTopList.value[level - 1] + +menuHeightList.value[level - 1] > window.innerHeight) {
			return window.innerHeight - +menuHeightList.value[level - 1] - 10 + 'px';
		} else {
			return +menuTopList.value[level - 1] + 'px';
		}
	};
});
// 计算left坐标
const leftComp = computed(() => {
	return (level: number) => {
		if (level === 3) {
			const level2ContextMenuDom = document.querySelector(`body > #level2ContextMenu-${props.id.replace('#', '')}`) as HTMLElement;
			const rect = level2ContextMenuDom?.getBoundingClientRect() || {};
			const parent = window.innerWidth - rect.left;
			if (+menuLeftList.value[level - 1] + +menuWidthList.value[level - 1] > window.innerWidth) {
				return window.innerWidth - +menuWidthList.value[level - 1] - parent + 2 + 'px';
			} else {
				return +menuLeftList.value[level - 1] + 'px';
			}
		}
		if (level === 2) {
			const level1ContextMenuDom = document.querySelector(`body > #${props.id.replace('#', '')}`) as HTMLElement;
			const rect = level1ContextMenuDom?.getBoundingClientRect() || {};
			const parent = window.innerWidth - rect.left;
			if (+menuLeftList.value[level - 1] + +menuWidthList.value[level - 1] > window.innerWidth) {
				return window.innerWidth - +menuWidthList.value[level - 1] - parent + 2 + 'px';
			} else {
				return +menuLeftList.value[level - 1] + 'px';
			}
		}
		if (+menuLeftList.value[level - 1] + +menuWidthList.value[level - 1] > window.innerWidth) {
			return window.innerWidth - +menuWidthList.value[level - 1] - 10 + 'px';
		} else {
			return +menuLeftList.value[level - 1] + 'px';
		}
	};
});

// 点击菜单项
const click = (menuItem: MenuDataType, level: number) => {
	emits('click', {
		level,
		menu: menuItem,
	});

	if (menuItem?.children?.length && level < 3) return;
	level1ContextMenuVisible.value = false;
	level2ContextMenuVisible.value = false;
	level3ContextMenuVisible.value = false;
};
// 鼠标移入菜单项
const hover = (menuItem: MenuDataType, level: number) => {
	emits('hover', {
		level,
		menu: menuItem,
	});

	if (menuItem?.children?.length && level < 3) {
		activeMenuList.value = getParentNodeList(props.menu, menuItem, 'value');
		let menuDomId = `${props.id.replace('#', '')}`;
		if (level === 2) menuDomId = 'level2ContextMenu-' + menuDomId;
		if (level === 3) menuDomId = 'level3ContextMenu-' + menuDomId;
		const dom = document.querySelector(`#${menuDomId} #level` + `${level}-${menuItem.value || menuItem.label}`) as HTMLElement;
		const rect = dom.getBoundingClientRect();
		menuTopList.value[level] = rect.top;
		menuLeftList.value[level] = rect.left + dom.clientWidth + 5;
		if (level === 1) {
			level2ContextMenuVisible.value = true;
			level3ContextMenuVisible.value = false;
		} else if (level === 2) {
			level3ContextMenuVisible.value = true;
		}
	} else {
		if (level === 1) {
			level2ContextMenuVisible.value = false;
			level3ContextMenuVisible.value = false;
			activeMenuList.value = [];
		} else if (level === 2) {
			level3ContextMenuVisible.value = false;
			activeMenuList.value = [activeMenuList.value[0]];
		}
	}
};

// 获取各级菜单元素
const findContextMenuDomList = () => {
	const level1ContextMenuDom = document.querySelector(`body > #${props.id.replace('#', '')}`) as HTMLElement;
	const level2ContextMenuDom = document.querySelector(`body > #level2ContextMenu-${props.id.replace('#', '')}`) as HTMLElement;
	const level3ContextMenuDom = document.querySelector(`body > #level3ContextMenu-${props.id.replace('#', '')}`) as HTMLElement;
	ContextMenuDomList.value = [level1ContextMenuDom, level2ContextMenuDom, level3ContextMenuDom];
	ContextMenuDomList.value = ContextMenuDomList.value.filter((item) => item);
};

// 计算菜单整体高度
watch(
	() => level1ContextMenuVisible.value,
	(val) => {
		if (val) {
			nextTick(() => {
				findContextMenuDomList();
				menuWidthList.value[0] = ContextMenuDomList.value[0].clientWidth;
				menuHeightList.value[0] = ContextMenuDomList.value[0].clientHeight;
			});
		}
	}
);
watch(
	() => level2ContextMenuVisible.value,
	(val) => {
		if (val) {
			nextTick(() => {
				findContextMenuDomList();
				menuWidthList.value[1] = ContextMenuDomList.value[1].clientWidth;
				menuHeightList.value[1] = ContextMenuDomList.value[1].clientHeight;
			});
		}
	}
);
watch(
	() => level3ContextMenuVisible.value,
	(val) => {
		if (val) {
			nextTick(() => {
				findContextMenuDomList();
				menuWidthList.value[2] = ContextMenuDomList.value[2].clientWidth;
				menuHeightList.value[2] = ContextMenuDomList.value[2].clientHeight;
			});
		}
	}
);

const windowsClickListener = (e: MouseEvent) => {
	let insideTheContextMenuDom = false;
	ContextMenuDomList.value.forEach((dom: any) => {
		if (!insideTheContextMenuDom) insideTheContextMenuDom = dom.contains(e.target as Node);
	});
	if (!insideTheContextMenuDom) {
		// 左键点击菜单以外的地方
		level1ContextMenuVisible.value = false;
		level2ContextMenuVisible.value = false;
		level3ContextMenuVisible.value = false;
	}
};
const windowsContextMenuListener = (e: MouseEvent) => {
	// 获取所有合法区域元素
	RootDomList.value = document.querySelectorAll(`${props.enableMenuDom}`) as NodeListOf<HTMLElement>;
	// 获取全局所有菜单组件元素
	const AllContextMenuDomList = document.querySelectorAll('.ContextMenu') as NodeListOf<HTMLElement>;
	findContextMenuDomList();

	let insideTheEnableMenuDom = false;
	RootDomList.value?.forEach((dom: HTMLElement) => {
		if (!insideTheEnableMenuDom) insideTheEnableMenuDom = dom.contains(e.target as Node);
	});

	let insideTheContextMenuDom = false;
	ContextMenuDomList.value.forEach((dom: any) => {
		if (!insideTheContextMenuDom) insideTheContextMenuDom = dom.contains(e.target as Node);
	});

	if (insideTheEnableMenuDom) {
		// 右键点击法定区域
		e.preventDefault();
		e.stopPropagation();
		e.stopImmediatePropagation();
		level1ContextMenuVisible.value = false;
		level2ContextMenuVisible.value = false;
		level3ContextMenuVisible.value = false;

		AllContextMenuDomList.forEach((dom: any) => {
			dom.style.display = 'none';
		});

		setTimeout(() => {
			menuTopList.value[0] = e.clientY;
			menuLeftList.value[0] = e.clientX;
			level1ContextMenuVisible.value = true;
		}, 15);
	} else if (!insideTheEnableMenuDom && insideTheContextMenuDom) {
		// 右键点击非法区域，且点击的是右键菜单
		e.preventDefault();
		e.stopPropagation();
	} else if (!insideTheEnableMenuDom && !insideTheContextMenuDom) {
		// 右键点击非法区域，且点击的不是右键菜单
		level1ContextMenuVisible.value = false;
		level2ContextMenuVisible.value = false;
		level3ContextMenuVisible.value = false;
	}
};

onMounted(() => {
	if (!props.id) throw new Error('请传入组件id');
	findContextMenuDomList();
	window.addEventListener('click', windowsClickListener);
	window.addEventListener('contextmenu', windowsContextMenuListener);
});
onUnmounted(() => {
	window.removeEventListener('click', windowsClickListener);
	window.removeEventListener('contextmenu', windowsContextMenuListener);
});
</script>

<style scoped lang="scss">
.ContextMenu[data-theme='light'] {
	--bg: rgba(255, 255, 255, 0.7);
	--hoverBg: rgba(0, 0, 0, 0.05);
	--color-text: rgba(0, 0, 0, 0.7);
	--color-text-desc: rgba(0, 0, 0, 0.4);
	--color-text-highlight: rgba(0, 0, 0, 0.85);
}
.ContextMenu {
	--bg: rgba(0, 0, 0, 0.7);
	--hoverBg: rgba(255, 255, 255, 0.07);
	--color-text: rgba(255, 255, 255, 0.7);
	--color-text-desc: rgba(255, 255, 255, 0.4);
	--color-text-highlight: rgba(255, 255, 255, 1);
}
.ContextMenu {
	position: fixed;
	z-index: calc(99999 + 1);
	max-width: 450px;
	max-height: 80vh;
	overflow: auto;
	background-color: var(--bg);
	backdrop-filter: blur(40px);
	box-shadow: 0 2px 12px 0 #00000026;
	transform-origin: top;
	animation: visibleAtOpacity 0.175s ease-in-out forwards, visibleAtTransform 0.1s ease-in-out forwards;

	display: flex;
	flex-direction: column;
	gap: 3px;

	border: 1px solid var(--hoverBg);
	border-radius: 12px;
	padding: 8px;

	.menu {
		cursor: pointer;

		.box {
			.content {
				display: flex;
				justify-content: space-between;
				color: var(--color-text);
				position: relative;
				height: 32px;
				line-height: 32px;
				font-size: 13px;
				border-radius: 6px;
				padding: 0 10px;
				transition: all 0.15s;

				.left {
					display: flex;
					align-items: center;
					overflow: hidden;

					.iconBox {
						width: calc(13px + 5px);
						display: flex;
						align-items: center;
					}

					& > span {
						overflow: hidden;
						text-overflow: ellipsis;
						white-space: nowrap;
					}
				}

				.right {
					color: var(--color-text-desc);
				}

				&:hover {
					color: var(--color-text-highlight);
					background-color: var(--hoverBg);
				}
			}

			&.boxSlot {
				padding: 0;
			}
		}

		.divider {
			width: calc(100% - 20px);
			height: 1px;
			background-color: var(--hoverBg);
			margin: 3px 10px 0;
		}

		&[data-disabled='true'] {
			cursor: not-allowed;

			.box {
				pointer-events: none;

				& .content {
					color: var(--color-text-desc);
				}
			}
		}

		&.activeMenu {
			.content {
				color: var(--color-text-highlight);
				background-color: var(--hoverBg);
			}
		}
	}

	&.ContextMenuChildren {
		min-width: 150px;
	}
	&.ContextMenuLevel2 {
		z-index: calc(99999 + 2);
	}
	&.ContextMenuLevel3 {
		z-index: calc(99999 + 3);
	}

	&::-webkit-scrollbar {
		display: none;
	}
}

@keyframes visibleAtOpacity {
	0% {
		opacity: 0;
	}
	100% {
		opacity: 1;
	}
}
@keyframes visibleAtTransform {
	0% {
		transform: scaleY(0);
	}
	100% {
		transform: scaleY(1);
	}
}
</style>
